/*
 * Toedo
 * Copyright (C) 2009-2010 J. Devauchelle and contributors.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 3 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

package org.toedo.model.export;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.toedo.gui.translations.Messages;
import org.toedo.logging.Log;
import org.toedo.model.Attachment;
import org.toedo.model.Project;
import org.toedo.model.Tag;
import org.toedo.model.Task;
import org.toedo.utils.Constants;
import org.toedo.utils.DateUtils;

public class HtmlGenerator implements IRunnableWithProgress {
	
	public static final String DEFAULT_CSS_PATH = "org/toedo/resources/css/default-export.css"; //$NON-NLS-1$
	
	private static final String XHTML_DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"; //$NON-NLS-1$
	private static final String XHTML_HTML_OPEN = "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n"; //$NON-NLS-1$
	private static final String XHTML_HTML_CLOSE = "</html>\n"; //$NON-NLS-1$
	private static final String XHTML_HEAD_OPEN = "<head>\n"; //$NON-NLS-1$
	private static final String XHTML_HEAD_CLOSE = "</head>\n"; //$NON-NLS-1$
	private static final String XHTML_META = "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"; //$NON-NLS-1$
	private static final String XHTML_TITLE = "<title>%s</title>\n"; //$NON-NLS-1$
	private static final String XHTML_BODY_OPEN = "<body>\n"; //$NON-NLS-1$
	private static final String XHTML_BODY_CLOSE = "</body>\n"; //$NON-NLS-1$
	
	private static final String XHTML_H1 = "<h1>%s</h1>\n"; //$NON-NLS-1$
	private static final String XHTML_H2 = "<h2>%s</h2>\n"; //$NON-NLS-1$
	private static final String XHTML_H3 = "<h3>%s</h3>\n"; //$NON-NLS-1$
	private static final String XHTML_H4 = "<h4>%s</h4>\n"; //$NON-NLS-1$
	
	private static final String XHTML_DIV_ID_OPEN = "<div id=\"%s\">\n"; //$NON-NLS-1$
	private static final String XHTML_DIV_CLASS_OPEN = "<div class=\"%s\">\n"; //$NON-NLS-1$
	private static final String XHTML_DIV_CLASS_ID_OPEN = "<div class=\"%s\" id=\"%s\">\n"; //$NON-NLS-1$
	private static final String XHTML_DIV_CLOSE = "</div>\n"; //$NON-NLS-1$
	
	private static final String XHTML_P_CLASS_OPEN = "<p class=\"%s\">"; //$NON-NLS-1$
	private static final String XHTML_P_OPEN = "<p>"; //$NON-NLS-1$
	private static final String XHTML_P_CLOSE = "</p>\n"; //$NON-NLS-1$
	
	private static final String XHTML_UL_OPEN = "<ul>"; //$NON-NLS-1$
	private static final String XHTML_UL_CLOSE = "</ul>\n"; //$NON-NLS-1$
	
	private static final String XHTML_LI_OPEN = "<li>"; //$NON-NLS-1$
	private static final String XHTML_LI_CLOSE = "</li>\n"; //$NON-NLS-1$
	
	private static final String XHTML_A = "<a href=\"%s\">%s</a>\n"; //$NON-NLS-1$
	
	private static final String XHTML_STYLE_OPEN = "<style type=\"text/css\">\n"; //$NON-NLS-1$
	private static final String XHTML_STYLE_CLOSE = "</style>\n"; //$NON-NLS-1$
	
	private static final String TASK_ID_PREFIX = "taskId-"; //$NON-NLS-1$
	
	private Project mProject;
	private String mOutputFileName;
	private boolean mIncludeClosedTasks;
	private boolean mIncludeCss;
	private String mCssData;
	private boolean mIncludeToc;
	
	public HtmlGenerator(Project project, String outputFileName, boolean includeToc, boolean includeClosedTasks, boolean includeCss, String cssData) {
		mProject = project;
		mOutputFileName = outputFileName;
		mIncludeToc = includeToc;
		mIncludeClosedTasks = includeClosedTasks;
		mIncludeCss = includeCss;
		mCssData = cssData;
	}
	
	public HtmlGenerator(Project project, boolean includeToc, boolean includeClosedTasks, boolean includeCss, String cssData) {
		this(project, null, includeToc, includeClosedTasks, includeCss, cssData);
	}
	
	private List<Task> internalGetTasksList() {
		return mProject.getTasksList(mIncludeClosedTasks);
	}
	
	private void buildHeaders(StringBuilder sb) {
		sb.append(XHTML_DOCTYPE);
		sb.append(XHTML_HTML_OPEN);
		sb.append(XHTML_HEAD_OPEN);
		sb.append(XHTML_META);
		sb.append(String.format(XHTML_TITLE, mProject.getProjectName()));
						
		if (mIncludeCss) {
			
			if (mCssData != null) {				
				
				sb.append(XHTML_STYLE_OPEN);
				sb.append(mCssData);
				sb.append(XHTML_STYLE_CLOSE);
				
			} else {			
				InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(DEFAULT_CSS_PATH);
				if (stream != null) {
					sb.append(XHTML_STYLE_OPEN);

					try {
						InputStreamReader streamReader = new InputStreamReader(stream);
						BufferedReader buffer = new BufferedReader(streamReader);						

						String line = null;
						while ( null != (line = buffer.readLine())){
							sb.append(line + "\n"); //$NON-NLS-1$
						}
					} catch (IOException e) {
						Log.getInstance(HtmlGenerator.class).warn("Unable to include default css."); //$NON-NLS-1$
					} finally {

						sb.append(XHTML_STYLE_CLOSE);
					}
				}
			}
		}
		
		sb.append(XHTML_HEAD_CLOSE);
		sb.append(XHTML_BODY_OPEN);
	}
	
	private void buildEndOfFile(StringBuilder sb) {
		sb.append(XHTML_BODY_CLOSE);
		sb.append(XHTML_HTML_CLOSE);
	}
	
	private void buildTask(StringBuilder sb, Task task) {
		sb.append(String.format(XHTML_DIV_CLASS_ID_OPEN, "task", TASK_ID_PREFIX + task.getId().toString())); //$NON-NLS-1$
		
		sb.append(String.format(XHTML_H3, task.getTitle()));
		
		sb.append(String.format(XHTML_DIV_CLASS_OPEN, "taskDetails")); //$NON-NLS-1$
		
		sb.append(String.format(XHTML_P_CLASS_OPEN, "startDate")); //$NON-NLS-1$
		sb.append(String.format("%s %s", Messages.HtmlGenerator_StartDate, DateUtils.dateToDisplayString(task.getStartDate()))); //$NON-NLS-1$
		sb.append(XHTML_P_CLOSE);
		
		sb.append(String.format(XHTML_P_CLASS_OPEN, "dueDate")); //$NON-NLS-1$
		
		String dueDateMessage;
		int delta = task.getDeltaInDays();
		if (delta >= 0) {
			dueDateMessage = String.format(Messages.TaskView_DueDateLabelOnTime, DateUtils.dateToDisplayString(task.getDueDate()), Integer.toString(task.getDeltaInDays()));
		} else {
			dueDateMessage = String.format(Messages.TaskView_DueDateLabelLate, DateUtils.dateToDisplayString(task.getDueDate()), Integer.toString(- task.getDeltaInDays()));
		}
		
		sb.append(String.format("%s %s", Messages.HtmlGenerator_DueDate, dueDateMessage)); //$NON-NLS-1$
		sb.append(XHTML_P_CLOSE);
		
		sb.append(String.format(XHTML_P_CLASS_OPEN, "taskPriority")); //$NON-NLS-1$
		sb.append(String.format("%s %s", Messages.HtmlGenerator_Priority, task.getPriority())); //$NON-NLS-1$
		sb.append(XHTML_P_CLOSE);
		
		boolean first = true;
		StringBuilder tagsSb = new StringBuilder();
		Iterator<Tag> iter = task.getTagsList().iterator();
		while (iter.hasNext()) {
			if (first) {
				tagsSb.append(iter.next().getName());
				first = false;
			} else {
				tagsSb.append(", " + iter.next().getName()); //$NON-NLS-1$
			}
		}
		
		sb.append(String.format(XHTML_P_CLASS_OPEN, "taskTags")); //$NON-NLS-1$
		sb.append(String.format("%s %s", Messages.HtmlGenerator_Tags, tagsSb.toString())); //$NON-NLS-1$
		sb.append(XHTML_P_CLOSE);
		
		sb.append(XHTML_DIV_CLOSE);
		
		String taskContent = task.getHtmlContent();
		if ((taskContent != null) &&
				(!taskContent.isEmpty())) {
			sb.append(String.format(XHTML_DIV_CLASS_OPEN, "taskContent")); //$NON-NLS-1$
		
			sb.append(taskContent);
			
			sb.append(XHTML_DIV_CLOSE);
		}
		
		if (task.getAttachmentList().size() > 0) {
			sb.append(String.format(XHTML_DIV_CLASS_OPEN, "taskAttachments")); //$NON-NLS-1$

			sb.append(String.format(XHTML_H4, Messages.HtmlGenerator_Attachements));
			
			sb.append(XHTML_UL_OPEN);
			
			for (Attachment attachment : task.getAttachmentList()) {
				sb.append(XHTML_LI_OPEN);
				sb.append(String.format(XHTML_A, attachment.getLink(), attachment.getTitle()));
				sb.append(XHTML_LI_CLOSE);
			}
			
			sb.append(XHTML_UL_CLOSE);
			
			sb.append(XHTML_DIV_CLOSE);
		}
		
		sb.append(XHTML_DIV_CLOSE);
	}
	
	private void buildTasksList(StringBuilder sb) {
		sb.append(String.format(XHTML_DIV_ID_OPEN, "tasksList")); //$NON-NLS-1$
		
		sb.append(String.format(XHTML_H2, "Tasks list")); //$NON-NLS-1$
		
		for (Task task : internalGetTasksList()) {
			buildTask(sb, task);
		}
		
		sb.append(XHTML_DIV_CLOSE);
	}
	
	private void buildToc(StringBuilder sb) {
		sb.append(String.format(XHTML_DIV_ID_OPEN, "tasksToc")); //$NON-NLS-1$
		
		sb.append(String.format(XHTML_H4, Messages.HtmlGenerator_TOC));
		
		sb.append(XHTML_UL_OPEN);
		
		for (Task task : internalGetTasksList()) {
			sb.append(XHTML_LI_OPEN);
			sb.append(String.format(XHTML_A, "#" + TASK_ID_PREFIX + task.getId().toString(), task.getTitle())); //$NON-NLS-1$
			sb.append(XHTML_LI_CLOSE);
		}
		
		sb.append(XHTML_UL_CLOSE);
		
		sb.append(XHTML_DIV_CLOSE);
	}
	
	private void buildFooter(StringBuilder sb) {
		sb.append(String.format(XHTML_DIV_ID_OPEN, "footer")); //$NON-NLS-1$
		
		sb.append(XHTML_P_OPEN);
		sb.append(String.format("Generated by %s version %s on %s.", Constants.APP_NAME, Constants.APP_VERSION, DateUtils.dateTimeToDisplayString(new Date())));
		sb.append(XHTML_P_CLOSE);
		
		sb.append(XHTML_DIV_CLOSE);
	}
	
	private void buildContent(StringBuilder sb) {
		sb.append(String.format(XHTML_DIV_ID_OPEN, "content")); //$NON-NLS-1$
		
		sb.append(String.format(XHTML_DIV_ID_OPEN, "projectTitle")); //$NON-NLS-1$
		sb.append(String.format(XHTML_H1, mProject.getProjectName()));
		sb.append(XHTML_DIV_CLOSE);
		
		if (mIncludeToc) {
			buildToc(sb);
		}
		
		buildTasksList(sb);
		
		sb.append(XHTML_DIV_CLOSE);
	}
	
	public String getHtml() {
		StringBuilder sb = new StringBuilder();
		
		buildHeaders(sb);
		buildContent(sb);
		buildFooter(sb);
		buildEndOfFile(sb);
		
		return sb.toString();
	}

	@Override
	public void run(IProgressMonitor monitor) throws InvocationTargetException,	InterruptedException {
		
		if ((mOutputFileName != null) &&
				(!mOutputFileName.isEmpty())) {
			monitor.beginTask(Messages.HtmlGenerator_MonitorTitle, 2);

			String html = getHtml();		
			monitor.worked(1);
			
			OutputStreamWriter osw;
			try {
				osw = new OutputStreamWriter(new FileOutputStream(mOutputFileName), "UTF-8"); //$NON-NLS-1$
		
				osw.write(html);
				osw.flush();
				osw.close();
				
			} catch (UnsupportedEncodingException e) {
				Log.getInstance(HtmlGenerator.class).warn("Error in HTML export: UnsupportedEncodingException:" + e.getMessage()); //$NON-NLS-1$
			} catch (FileNotFoundException e) {
				Log.getInstance(HtmlGenerator.class).warn("Error in HTML export: FileNotFoundException:" + e.getMessage()); //$NON-NLS-1$
			} catch (IOException e) {
				Log.getInstance(HtmlGenerator.class).warn("Error in HTML export: IOException:" + e.getMessage()); //$NON-NLS-1$
			}
			
			monitor.worked(1);
			
			monitor.done();
			
		}
	}

}
